package com.github.zyf.ioaj.algorithm.swarmintelligence;

import com.github.zyf.ioaj.algorithm.Input;
import com.github.zyf.ioaj.algorithm.Output;
import com.github.zyf.ioaj.annotation.internal.Algorithm;
import com.github.zyf.ioaj.annotation.internal.BasicParam;
import com.github.zyf.ioaj.annotation.internal.TempParam;
import com.github.zyf.ioaj.constant.FileType;
import com.github.zyf.ioaj.constant.ParamType;
import com.github.zyf.ioaj.export.Report;
import com.github.zyf.ioaj.export.Tracker;
import com.github.zyf.ioaj.utils.basedata.MathUtils;

import java.util.Random;

/**
 * <h1>Cuckoo Search</h1>
 * <p>布谷鸟算法</p>
 * <p>默认求解最小值，如果需要求解最大值，将目标函数乘以-1即可，最后得到的值再次乘以-1就是得到的最优值（自变量取值不用乘以-1）</p>
 * <p>需要计算则调用{@link #go()}</p>
 * <p>需要跟踪每次迭代则调用{@link #tracker(String, String, String, FileType)}</p>
 * <p>需要生成报告则调用{@link #report(String, String, String, FileType)}</p>
 * <p>算法背景：</p>
 * <p>
 * 谷鸟本身并不会孵卵，它们会机智的将卵产在其他鸟类的巢穴中，等孩子还没出生就给它找好养父母。
 * 一般来说，布谷鸟蛋的孵化时间比寄主所产下的蛋稍早。
 * 一旦第一只布谷鸟孵化出来，它将采取的第一个本能行动是盲目地将寄主卵推出巢(布谷鸟妈妈产蛋时也会这样做)，
 * 因此增加了布谷鸟从寄主鸟那里获得的食物量。
 * 研究还表明，小布谷鸟也可以模仿寄主幼崽的叫声来获得更多的进食机会。
 * </p>
 * <p>
 * 布谷鸟算法提出者之一的Xin-She Yang教授试了多组参数，发现种群规模在15到40，
 * 布谷鸟蛋被发现的概率 pa 取0.25时，对于大多数优化问题已足够，
 * 并且结果和分析也表明收敛速度在一定程度上对所使用的参数不敏感。
 * </p>
 *
 * @author zyf
 */
@Algorithm
public class CS extends SwarmIntelligenceAlgorithm {

    /**
     * 莱维飞行步长
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_2, desc = "莱维飞行步长")
    private double[][] levy_flight_step;

    /**
     * 鸟巢位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_2, desc = "鸟巢位置")
    private double[][] nests;

    /**
     * 每个鸟巢对应的函数值（适应度）
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "每个鸟巢对应的函数值")
    private double[] nests_f;

    /**
     * 莱维飞行参数
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "莱维飞行参数")
    private double lambda;

    /**
     * 步长比例算子
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "步长比例算子")
    private double alpha;

    /**
     * 游走控制参数
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "游走控制参数")
    private double P_a;

    /**
     * 当前全局最优位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "当前全局最优位置")
    private double[] x;

    /**
     * 当前全局最优解
     */
    @TempParam(type = ParamType.DOUBLE, desc = "当前全局最优解")
    private double y;

    /**
     * 抽象算法构造函数，需要提供输入量，即提前初始化好{@link Input}对象
     *
     * @param input {@link Input}
     */
    public CS(Input input) {
        super(input);
        init();
    }

    private void init() {
        setIter(1000);
        this.lambda = 1.5;
        this.P_a = 0.25;
        this.alpha = 1;
        this.y = 0;
        int n = getN();
        int D = getInput().getVarNum();

        this.levy_flight_step = new double[n][D];
        this.nests = new double[n][D];
        this.nests_f = new double[n];
        this.x = new double[D];

        int i, j;
        for (i = 0; i < n; i++) {
            for (j = 0; j < D; j++) {
                nests[i][j] = getInput().randomValueInRange(j);
                levy_flight_step[i][j] = 0;
            }
            nests_f[i] = getInput().getFunction().apply(nests[i]);
        }
        this.x = nests[0].clone();
        this.y = nests_f[0];
    }

    /**
     * 每次迭代时扫描一遍
     */
    public void scan() {
        update_nests();
        abandon_nests();
    }

    /**
     * 生成最优解
     */
    private void generateOptimization() {
        setOutput(new Output(y, x));
    }

    /**
     * 运行并计算结果
     */
    @Override
    public void go() {
        int t;
        for (t = 0; t < getIter(); t++) {
            scan();
        }
        generateOptimization();
    }

    /**
     * 运行并计算结果（按迭代逐步跟踪，自带运行，无需手动），如果目的是计算出结果，请使用{@link #go()}
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Tracker}
     */
    @Override
    public Tracker tracker(String author, String message, String title, FileType fileType) {
        int t;
        Tracker tracker = new Tracker(this, author, message, title, fileType);
        tracker.startTracker();
        for (t = 0; t < getIter(); t++) {
            scan();
            tracker.trackOne(t + 1);
        }
        generateOptimization();
        tracker.endTracker();
        return tracker;
    }

    /**
     * 生成报告（要先手动运行才能生成报告）
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Report}
     */
    @Override
    public Report report(String author, String message, String title, FileType fileType) {
        return new Report(this, author, message, title, fileType);
    }

    /**
     * 更新巢穴
     */
    private void update_nests() {
        levy_flight();
        int i, j;
        int n = getN();
        int D = getInput().getVarNum();
        // 新巢位置
        double[] new_nests;
        double new_nests_f;
        // 步长大小和方向
        double step_size;
        double step_direction;
        for (i = 0; i < n; i++) {
            new_nests = nests[i].clone();
            for (j = 0; j < D; j++) {
                // 随机游走
                step_size = alpha * levy_flight_step[i][j] * (nests[i][j] - x[j]);
                step_direction = Math.random() * D;
                new_nests[j] += step_size * step_direction;
                // 防止越界
                if (new_nests[j] < getInput().getLb()[j]) new_nests[j] = getInput().getLb()[j];
                if (new_nests[j] > getInput().getUb()[j]) new_nests[j] = getInput().getUb()[j];
            }
            new_nests_f = getInput().getFunction().apply(new_nests);
            if (new_nests_f < y) {
                x = nests[i].clone();
                y = new_nests_f;
                nests[i] = new_nests.clone();
                nests_f[i] = new_nests_f;
            }
        }
    }

    /**
     * 策略放弃巢穴
     */
    private void abandon_nests() {
        int i, j;
        int n = getN();
        int D = getInput().getVarNum();
        // 步长大小和方向
        double step_size;
        // 随机
        Random r = new Random();
        for (i = 0; i < n; i++) {
            if (Math.random() < P_a) {
                for (j = 0; j < D; j++) {
                    step_size = Math.random() * (nests[r.nextInt(n)][j] - nests[r.nextInt(n)][j]);
                    nests[i][j] += step_size;
                    // 防止越界
                    if (nests[i][j] < getInput().getLb()[j]) nests[i][j] = getInput().getLb()[j];
                    if (nests[i][j] > getInput().getUb()[j]) nests[i][j] = getInput().getUb()[j];
                }
            }
        }
    }

    private void levy_flight() {
        double a = MathUtils.gamma(1 + lambda) * Math.sin(Math.PI * lambda / 2) / (MathUtils.gamma((1 + lambda) / 2) * lambda * (Math.pow(2, (lambda - 1) / 2)));
        double sigma_u = Math.pow(a, (1 / lambda));
        Random random = new Random();
        double u;
        double v;
        int i, j;
        int n = getN();
        int D = getInput().getVarNum();
        for (i = 0; i < n; i++) {
            for (j = 0; j < D; j++) {
                u = Math.sqrt(sigma_u) * random.nextGaussian();
                v = random.nextGaussian();
                levy_flight_step[i][j] = u / (Math.pow(Math.abs(v), (1 / lambda)));
            }
        }
    }
}
